package com.leyou.auth.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.auth.dto.Payload;
import com.leyou.auth.dto.UserDetails;
import com.leyou.common.exceptions.LyException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.leyou.auth.constants.JwtConstants.JTI_KEY_PREFIX;

/**
 * @author 虎哥
 */
@Slf4j
public class JwtUtils {

    /**
     * JWT解析器
     */
    private final JwtParser jwtParser;
    /**
     * 秘钥
     */
    private final SecretKey secretKey;

    private final StringRedisTemplate redisTemplate;

    private final static ObjectMapper mapper = new ObjectMapper();

    public JwtUtils(String key, StringRedisTemplate redisTemplate) {
        // 生成秘钥
        secretKey = Keys.hmacShaKeyFor(key.getBytes(Charset.forName("UTF-8")));
        this.redisTemplate = redisTemplate;
        // JWT解析器
        this.jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
    }

    /**
     * 生成jwt，用默认的JTI
     *
     * @param userDetails 用户信息
     * @return JWT
     */
    public String createJwt(UserDetails userDetails) {
        return createJwt(userDetails, 1800);
    }

    /**
     * 生成jwt，自己指定的过期时间
     *
     * @param userDetails 用户信息
     * @return JWT
     */
    public String createJwt(UserDetails userDetails, int expireSeconds) {
        try {
            // 生成JTI
            String jti = createJti();
            // 生成token
            String jwt = Jwts.builder().signWith(secretKey)
                    .setId(jti)
                    .claim("user", mapper.writeValueAsString(userDetails))
                    .compact();
            // 把jti存入redis
            redisTemplate.opsForValue().set(JTI_KEY_PREFIX + userDetails.getId(), jti, expireSeconds, TimeUnit.SECONDS);
            return jwt;
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 验证并解析jwt，返回包含用户信息的载荷
     *
     * @param jwt   token
     * @return 载荷，包含JTI和用户信息
     */
    public Payload parseJwt(String jwt) {
        try {
            // 1.验证JWT
            Jws<Claims> claimsJws = jwtParser.parseClaimsJws(jwt);
            Claims claims = claimsJws.getBody();
            // 2.解析载荷
            Payload payload = new Payload();
            payload.setJti(claims.getId());
            payload.setUserDetail(mapper.readValue(claims.get("user", String.class), UserDetails.class));
            // 3.去redis验证
            // 3.1.获取userId
            Long userId = payload.getUserDetail().getId();
            // 3.2.从redis中，根据userId查询JTI
            String cacheJTI = redisTemplate.opsForValue().get(JTI_KEY_PREFIX + userId);
            // 3.3.将redis中的JTI跟JWT的JTI比较
            if(cacheJTI == null){
                throw new LyException(401, "登录已过期！");
            }
            if(!StringUtils.equals(cacheJTI, payload.getJti())){
                throw new LyException(401, "您的账号已在别处登录！");
            }
            return payload;
        }catch (IllegalArgumentException e) {
            throw new LyException(401, "用户未登录！");
        }catch (JwtException e) {
            throw new LyException(401, "登录无效或者已经超时！");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String createJti() {
        return StringUtils.replace(UUID.randomUUID().toString(), "-", "");
    }

    public void refreshJwt(String jwt) {
        try {
            // 1.解析jwt
            Payload payload = parseJwt(jwt);
            // 2.获取用户信息
            Long userId = payload.getUserDetail().getId();
            // 3.刷新有效期
            redisTemplate.expire(JTI_KEY_PREFIX + userId, 1800, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("刷新有效期异常", e);
        }
    }

    public void deleteJwt(Long userId) {
        redisTemplate.delete(JTI_KEY_PREFIX + userId);
    }
}